/****************************************************************************
 * drivers/cpufreq/cpufreq_procfs.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/procfs.h>
#include <nuttx/cpufreq.h>

#include "cpufreq_internal.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Determines the size of an intermediate buffer that must be large enough
 * to handle the longest line generated by this logic.
 */

#define CPUFREQ_LINELEN 256

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This structure describes one open "file" */

struct cpufreq_file_s
{
  struct cpufreq_policy *policy;
  struct procfs_file_s  base;   /* Base open file structure */
  unsigned int linesize;        /* Number of valid characters in line[] */
  char line[CPUFREQ_LINELEN];   /* Pre-allocated buffer for formatted lines */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* File system methods */

static int     cpufreq_open (FAR struct file *filep,
                             FAR const char *relpath,
                             int oflags, mode_t mode);
static int     cpufreq_close(FAR struct file *filep);
static ssize_t cpufreq_read (FAR struct file *filep,
                             FAR char *buffer,
                             size_t buflen);
static ssize_t cpufreq_write(FAR struct file *filep,
                             FAR const char *buffer,
                             size_t buflen);
static int     cpufreq_dup  (FAR const struct file *oldp,
                             FAR struct file *newp);
static int     cpufreq_stat (FAR const char *relpath,
                             FAR struct stat *buf);

/****************************************************************************
 * Public Data
 ****************************************************************************/

const struct procfs_operations g_cpufreq_operations =
{
  .open       = cpufreq_open,         /* open */
  .close      = cpufreq_close,        /* close */
  .read       = cpufreq_read,         /* read */
  .write      = cpufreq_write,        /* write */
  .poll       = NULL,                 /* poll */
  .dup        = cpufreq_dup,          /* dup */
  .opendir    = NULL,                 /* opendir */
  .closedir   = NULL,                 /* closedir */
  .readdir    = NULL,                 /* readdir */
  .rewinddir  = NULL,                 /* rewinddir */
  .stat       = cpufreq_stat          /* stat */
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: cpufreq_open
 ****************************************************************************/

static int cpufreq_open(FAR struct file *filep, FAR const char *relpath,
                        int oflags, mode_t mode)
{
  FAR struct cpufreq_file_s *attr;

  finfo("Open '%s'\n", relpath);

  /* Allocate a container to hold the file attributes */

  attr = kmm_zalloc(sizeof(struct cpufreq_file_s));
  if (!attr)
    {
      ferr("ERROR: Failed to allocate file attributes\n");
      return -ENOMEM;
    }

  /* Save the attributes as the open-specific state in filep->f_priv */

  filep->f_priv = attr;
  return OK;
}

/****************************************************************************
 * Name: cpufreq_close
 ****************************************************************************/

static int cpufreq_close(FAR struct file *filep)
{
  FAR struct cpufreq_file_s *attr;

  /* Recover our private data from the struct file instance */

  attr = (FAR struct cpufreq_file_s *)filep->f_priv;
  DEBUGASSERT(attr);

  /* Release the file attributes structure */

  kmm_free(attr);
  filep->f_priv = NULL;
  return OK;
}

/****************************************************************************
 * Name: cpufreq_read
 ****************************************************************************/

static ssize_t cpufreq_read(FAR struct file *filep,
                            FAR char *buffer, size_t buflen)
{
  size_t linesize;
  size_t copysize;
  off_t offset;
  char line[CPUFREQ_LINELEN];
  FAR struct cpufreq_policy *policy;
#ifdef CONFIG_CPUFREQ_PROCFS_QOS
  FAR struct cpufreq_qos *qos;
#endif

  policy = cpufreq_policy_get();
  if (policy == NULL)
    {
      return 0;
    }

  offset = filep->f_pos;
  linesize = snprintf(line, CPUFREQ_LINELEN,
                      "cpu_cur_freq:%d\n"
                      "cpu_min_freq:%d\n"
                      "cpu_max_freq:%d\n"
                      "policy_cur_freq:%d\n"
                      "policy_min_freq:%d\n"
                      "policy_max_freq:%d\n"
                      "governor:%s\n",
                      cpufreq_get(policy),
                      policy->min_freq,
                      policy->max_freq,
                      policy->cur,
                      policy->min,
                      policy->max,
                      policy->governor->name);

#ifdef CONFIG_CPUFREQ_PROCFS_QOS

  linesize += snprintf(line + linesize, CPUFREQ_LINELEN - linesize,
                       "request list:\n");
  list_for_every_entry(&policy->qos_list, qos,
                       struct cpufreq_qos, node)
    {
      linesize += snprintf(line + linesize,
                           CPUFREQ_LINELEN - linesize,
                           "%p: %"PRId32" %"PRId32"\n",
                           qos->caller,
                           freq_qos_read_value(qos->min.qos, FREQ_QOS_MIN),
                           freq_qos_read_value(qos->max.qos, FREQ_QOS_MAX));
    }

#endif /* CONFIG_CPUFREQ_PROCFS_QOS */

  copysize = procfs_memcpy(line, linesize, buffer, buflen, &offset);
  filep->f_pos += copysize;

  return copysize;
}

/****************************************************************************
 * Name: cpufreq_write
 ****************************************************************************/

static ssize_t cpufreq_write(FAR struct file *filep,
                             FAR const char *buffer, size_t buflen)
{
  FAR struct cpufreq_policy *policy;
  ssize_t ret;
  int min;
  int max;

  policy = cpufreq_policy_get();
  ret = sscanf(buffer, "%d,%d", &min, &max);
  if (ret == 1)
    {
      max = min;
    }
  else if (ret != 2)
    {
      return -EINVAL;
    }

  ret = freq_qos_update_request(&policy->min_freq_req, min);
  if (ret < 0)
    {
      return ret;
    }

  ret = freq_qos_update_request(&policy->max_freq_req, max);
  if (ret < 0)
    {
      return ret;
    }

  ret = buflen;
  return ret;
}

/****************************************************************************
 * Name: cpufreq_dup
 *
 * Description:
 *   Duplicate open file data in the new file structure.
 *
 ****************************************************************************/

static int cpufreq_dup(FAR const struct file *oldp, FAR struct file *newp)
{
  FAR struct cpufreq_file_s *oldattr;
  FAR struct cpufreq_file_s *newattr;

  finfo("Dup %p->%p\n", oldp, newp);

  /* Recover our private data from the old struct file instance */

  oldattr = (FAR struct cpufreq_file_s *)oldp->f_priv;
  DEBUGASSERT(oldattr);

  /* Allocate a new container to hold the task and attribute selection */

  newattr = kmm_malloc(sizeof(struct cpufreq_file_s));
  if (!newattr)
    {
      ferr("ERROR: Failed to allocate file attributes\n");
      return -ENOMEM;
    }

  /* The copy the file attributes from the old attributes to the new */

  memcpy(newattr, oldattr, sizeof(struct cpufreq_file_s));

  /* Save the new attributes in the new file structure */

  newp->f_priv = newattr;
  return OK;
}

/****************************************************************************
 * Name: cpufreq_stat
 *
 * Description: Return information about a file or directory
 *
 ****************************************************************************/

static int cpufreq_stat(const char *relpath, struct stat *buf)
{
  /* "cpufreq" is the name for a read-only file */

  memset(buf, 0, sizeof(struct stat));
  buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/
